/**
 * @license
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import '../../../test/common-test-setup-karma.js';
import './gr-textarea.js';
import {html} from '@polymer/polymer/lib/utils/html-tag.js';

const basicFixture = fixtureFromElement('gr-textarea');

const monospaceFixture = fixtureFromTemplate(html`
<gr-textarea monospace="true"></gr-textarea>
`);

const hideBorderFixture = fixtureFromTemplate(html`
<gr-textarea hide-border="true"></gr-textarea>
`);

suite('gr-textarea tests', () => {
  let element;

  setup(() => {
    element = basicFixture.instantiate();
    sinon.stub(element.reporting, 'reportInteraction');
  });

  test('monospace is set properly', () => {
    assert.isFalse(element.classList.contains('monospace'));
  });

  test('hideBorder is set properly', () => {
    assert.isFalse(element.$.textarea.classList.contains('noBorder'));
  });

  test('emoji selector is not open with the textarea lacks focus', () => {
    element.$.textarea.selectionStart = 1;
    element.$.textarea.selectionEnd = 1;
    element.text = ':';
    assert.isFalse(!element.$.emojiSuggestions.isHidden);
  });

  test('emoji selector is not open when a general text is entered', () => {
    MockInteractions.focus(element.$.textarea);
    element.$.textarea.selectionStart = 9;
    element.$.textarea.selectionEnd = 9;
    element.text = 'some text';
    assert.isFalse(!element.$.emojiSuggestions.isHidden);
  });

  test('emoji selector opens when a colon is typed & the textarea has focus',
      () => {
        MockInteractions.focus(element.$.textarea);
        // Needed for Safari tests. selectionStart is not updated when text is
        // updated.
        element.$.textarea.selectionStart = 1;
        element.$.textarea.selectionEnd = 1;
        element.text = ':';
        flushAsynchronousOperations();
        assert.isFalse(element.$.emojiSuggestions.isHidden);
        assert.equal(element._colonIndex, 0);
        assert.isFalse(element._hideAutocomplete);
        assert.equal(element._currentSearchString, '');
      });

  test('emoji selector opens when a colon is typed after space',
      () => {
        MockInteractions.focus(element.$.textarea);
        // Needed for Safari tests. selectionStart is not updated when text is
        // updated.
        element.$.textarea.selectionStart = 2;
        element.$.textarea.selectionEnd = 2;
        element.text = ' :';
        flushAsynchronousOperations();
        assert.isFalse(element.$.emojiSuggestions.isHidden);
        assert.equal(element._colonIndex, 1);
        assert.isFalse(element._hideAutocomplete);
        assert.equal(element._currentSearchString, '');
      });

  test('emoji selector doesn\`t open when a colon is typed after character',
      () => {
        MockInteractions.focus(element.$.textarea);
        // Needed for Safari tests. selectionStart is not updated when text is
        // updated.
        element.$.textarea.selectionStart = 5;
        element.$.textarea.selectionEnd = 5;
        element.text = 'test:';
        flushAsynchronousOperations();
        assert.isTrue(element.$.emojiSuggestions.isHidden);
        assert.isTrue(element._hideAutocomplete);
      });

  test('emoji selector opens when a colon is typed and some substring',
      () => {
        MockInteractions.focus(element.$.textarea);
        // Needed for Safari tests. selectionStart is not updated when text is
        // updated.
        element.$.textarea.selectionStart = 1;
        element.$.textarea.selectionEnd = 1;
        element.text = ':';
        element.$.textarea.selectionStart = 2;
        element.$.textarea.selectionEnd = 2;
        element.text = ':t';
        flushAsynchronousOperations();
        assert.isFalse(element.$.emojiSuggestions.isHidden);
        assert.equal(element._colonIndex, 0);
        assert.isFalse(element._hideAutocomplete);
        assert.equal(element._currentSearchString, 't');
      });

  test('emoji selector opens when a colon is typed in middle of text',
      () => {
        MockInteractions.focus(element.$.textarea);
        // Needed for Safari tests. selectionStart is not updated when text is
        // updated.
        element.$.textarea.selectionStart = 1;
        element.$.textarea.selectionEnd = 1;
        // Since selectionStart is on Chrome set always on end of text, we
        // stub it to 1
        const text = ': hello';
        sinon.stub(element.$, 'textarea').value( {
          selectionStart: 1,
          value: text,
          textarea: {
            focus: () => {},
          },
        });
        element.text = text;
        flushAsynchronousOperations();
        assert.isFalse(element.$.emojiSuggestions.isHidden);
        assert.equal(element._colonIndex, 0);
        assert.isFalse(element._hideAutocomplete);
        assert.equal(element._currentSearchString, '');
      });
  test('emoji selector closes when text changes before the colon', () => {
    const resetStub = sinon.stub(element, '_resetEmojiDropdown');
    MockInteractions.focus(element.$.textarea);
    flushAsynchronousOperations();
    element.$.textarea.selectionStart = 10;
    element.$.textarea.selectionEnd = 10;
    element.text = 'test test ';
    element.$.textarea.selectionStart = 12;
    element.$.textarea.selectionEnd = 12;
    element.text = 'test test :';
    element.$.textarea.selectionStart = 15;
    element.$.textarea.selectionEnd = 15;
    element.text = 'test test :smi';

    assert.equal(element._currentSearchString, 'smi');
    assert.isFalse(resetStub.called);
    element.text = 'test test test :smi';
    assert.isTrue(resetStub.called);
  });

  test('_resetEmojiDropdown', () => {
    const closeSpy = sinon.spy(element, 'closeDropdown');
    element._resetEmojiDropdown();
    assert.equal(element._currentSearchString, '');
    assert.isTrue(element._hideAutocomplete);
    assert.equal(element._colonIndex, null);

    element.$.emojiSuggestions.open();
    flushAsynchronousOperations();
    element._resetEmojiDropdown();
    assert.isTrue(closeSpy.called);
  });

  test('_determineSuggestions', () => {
    const emojiText = 'tear';
    const formatSpy = sinon.spy(element, '_formatSuggestions');
    element._determineSuggestions(emojiText);
    assert.isTrue(formatSpy.called);
    assert.isTrue(formatSpy.lastCall.calledWithExactly(
        [{dataValue: '😂', value: '😂', match: 'tears :\')',
          text: '😂 tears :\')'},
        {dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
        ]));
  });

  test('_formatSuggestions', () => {
    const matchedSuggestions = [{value: '😢', match: 'tear'},
      {value: '😂', match: 'tears'}];
    element._formatSuggestions(matchedSuggestions);
    assert.deepEqual(
        [{value: '😢', dataValue: '😢', match: 'tear', text: '😢 tear'},
          {value: '😂', dataValue: '😂', match: 'tears', text: '😂 tears'}],
        element._suggestions);
  });

  test('_handleEmojiSelect', () => {
    element.$.textarea.selectionStart = 16;
    element.$.textarea.selectionEnd = 16;
    element.text = 'test test :tears';
    element._colonIndex = 10;
    const selectedItem = {dataset: {value: '😂'}};
    const event = {detail: {selected: selectedItem}};
    element._handleEmojiSelect(event);
    assert.equal(element.text, 'test test 😂');
  });

  test('_updateCaratPosition', () => {
    element.$.textarea.selectionStart = 4;
    element.$.textarea.selectionEnd = 4;
    element.text = 'test';
    element._updateCaratPosition();
    assert.deepEqual(element.$.hiddenText.innerHTML, element.text +
        element.$.caratSpan.outerHTML);
  });

  test('emoji dropdown is closed when iron-overlay-closed is fired', () => {
    const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
    element.$.emojiSuggestions.dispatchEvent(
        new CustomEvent('dropdown-closed', {
          composed: true, bubbles: true,
        }));
    assert.isTrue(resetSpy.called);
  });

  test('_onValueChanged fires bind-value-changed', () => {
    const listenerStub = sinon.stub();
    const eventObject = {currentTarget: {focused: false}};
    element.addEventListener('bind-value-changed', listenerStub);
    element._onValueChanged(eventObject);
    assert.isTrue(listenerStub.called);
  });

  suite('keyboard shortcuts', () => {
    function setupDropdown(callback) {
      MockInteractions.focus(element.$.textarea);
      element.$.textarea.selectionStart = 1;
      element.$.textarea.selectionEnd = 1;
      element.text = ':';
      element.$.textarea.selectionStart = 1;
      element.$.textarea.selectionEnd = 2;
      element.text = ':1';
      flushAsynchronousOperations();
    }

    test('escape key', () => {
      const resetSpy = sinon.spy(element, '_resetEmojiDropdown');
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
      assert.isFalse(resetSpy.called);
      setupDropdown();
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 27);
      assert.isTrue(resetSpy.called);
      assert.isFalse(!element.$.emojiSuggestions.isHidden);
    });

    test('up key', () => {
      const upSpy = sinon.spy(element.$.emojiSuggestions, 'cursorUp');
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
      assert.isFalse(upSpy.called);
      setupDropdown();
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 38);
      assert.isTrue(upSpy.called);
    });

    test('down key', () => {
      const downSpy = sinon.spy(element.$.emojiSuggestions, 'cursorDown');
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
      assert.isFalse(downSpy.called);
      setupDropdown();
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 40);
      assert.isTrue(downSpy.called);
    });

    test('enter key', () => {
      const enterSpy = sinon.spy(element.$.emojiSuggestions,
          'getCursorTarget');
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
      assert.isFalse(enterSpy.called);
      setupDropdown();
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
      assert.isTrue(enterSpy.called);
      flushAsynchronousOperations();
      assert.equal(element.text, '💯');
    });

    test('enter key - ignored on just colon without more information', () => {
      const enterSpy = sinon.spy(element.$.emojiSuggestions,
          'getCursorTarget');
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
      assert.isFalse(enterSpy.called);
      MockInteractions.focus(element.$.textarea);
      element.$.textarea.selectionStart = 1;
      element.$.textarea.selectionEnd = 1;
      element.text = ':';
      flushAsynchronousOperations();
      MockInteractions.pressAndReleaseKeyOn(element.$.textarea, 13);
      assert.isFalse(enterSpy.called);
    });
  });

  suite('gr-textarea monospace', () => {
  // gr-textarea set monospace class in the ready() method.
  // In Polymer2, ready() is called from the fixture(...) method,
  // If ready() is called again later, some nested elements doesn't
  // handle it correctly. A separate test-fixture is used to set
  // properties before ready() is called.

    let element;

    setup(() => {
      element = monospaceFixture.instantiate();
    });

    test('monospace is set properly', () => {
      assert.isTrue(element.classList.contains('monospace'));
    });
  });

  suite('gr-textarea hideBorder', () => {
  // gr-textarea set noBorder class in the ready() method.
  // In Polymer2, ready() is called from the fixture(...) method,
  // If ready() is called again later, some nested elements doesn't
  // handle it correctly. A separate test-fixture is used to set
  // properties before ready() is called.

    let element;

    setup(() => {
      element = hideBorderFixture.instantiate();
    });

    test('hideBorder is set properly', () => {
      assert.isTrue(element.$.textarea.classList.contains('noBorder'));
    });
  });
});

